package com.tskj.mh.badminton.aop;

import com.alibaba.fastjson.JSONObject;
import com.tskj.mh.badminton.aop.annotation.SystemControllerLog;
import com.tskj.mh.badminton.aop.service.SysLogsService;
import com.tskj.mh.badminton.utility.Tools;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
 * @notes:
 * @program: easystar2018_ss_v2.0
 * @author: JRX
 * @create: 2019-07-30 11:04
 * <p>
 * 执行顺序（前置→后置→最终）、（前置→异常→最终），异常和后置不可能同时出现只能出现一个
 * <p>
 * 此处暂时使用环绕通知方式，因为发现spring注解方式的（后置通知，最终通知）与（异常通知，最终通知）
 * 顺序存在问题可能会出现先最终通知在执行后置或异常通知，固更改用环绕通知方式
 * <p>
 * 写环绕通知就把（四种通知格式注解注释掉否则会执行两次）
 **/
@Aspect
@Component
public class SystemLogAspect {

    private Logger logger = Logger.getLogger(String.valueOf(this.getClass()));

    @Autowired
    private SysLogsService sysLogService;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

//    private Date visitTime; //开始时间
//    private Class clazz; //访问的类
//    private Method method;//访问的方法

    // 存在SQL注入风险
    private static final String IS_SQL_INJECTION = "输入参数存在SQL注入风险";

    private static final String UNVALIDATED_INPUT = "输入参数含有非法字符";

    private static final String ERORR_INPUT = "输入的参数非法";

    /**
     * Controller层切点 注解拦截
     */
    @Pointcut("@annotation(com.tskj.mh.badminton.aop.annotation.SystemControllerLog)")
    public void controllerAspect() {
    }
    /*  在使用spring框架配置AOP的时候，不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"
    例如定义切入点表达式 execution(* com.sample.ser.impl..*.*(..))
        execution()是最常用的切点函数，其语法如下所示：
        整个表达式可以分为五个部分：
        1、execution(): 表达式主体。
        2、第一个*号：表示返回类型，*号表示所有的类型。
        3、包名：表示需要拦截的包名，后面的两个句点表示当前包和当前包的所有子包，com.sample.ser.impl包、子孙包下所有类的方法。
        4、第二个*号：表示类名，*号表示所有的类。
        5、*(..):最后这个星号表示方法名，*号表示所有的方法，后面括弧里面表示方法的参数，两个句点表示任何参数。
    */

    /**
     * 方法规则拦截
     */
    @Pointcut("execution(* com.tskj.mh.badminton.controller..*.*(..))")
    public void controllerPointerCut() {
    }

    /**
     * 环绕通知
     * 只对所有的save接口进行注入
     * @param pjp
     */
    @Around("controllerAspect()")
    public Object aroundPringLog(ProceedingJoinPoint pjp) {
        //执行目标方法
        Object proceed = null;
        try {
//            // 得到方法所需的参数，获取访问的方法的参数
            Object[] args = pjp.getArgs();
//            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            //2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
//            String[] parameterNames = methodSignature.getParameterNames();
//            RequestMapping classAnnotation = pjp.getTarget().getClass().getAnnotation(RequestMapping.class);
//            String[] classValue = {""};
//            if (classAnnotation != null) {
//                classValue = classAnnotation.value();
//            }
//            logger.info("P_Names: " + Arrays.toString(parameterNames));
            logger.info("PARAMS:" + args[1].toString());
            JSONObject data = JSONObject.parseObject(args[1].toString());
            String startDate = Tools.today();
            if (data.containsKey("date")) {
                startDate = data.getString("date");
            }
            String startTime = data.getString("time");
            logger.info("startdate:" + startDate);
            logger.info("starttime:" + startTime);
            long starttime = System.currentTimeMillis();
            proceed = pjp.proceed(args);
            long exctime = System.currentTimeMillis() - starttime;
            JSONObject json = JSONObject.parseObject(proceed.toString());
            String result;

            Map<String, Object> logMap = new HashMap<>(6);

            if (json.getInteger("code") == 0) {
                result = "执行成功";
                logMap.put("detail", "");
            } else {
                result = "执行失败";
                logMap.put("detail", json.getString("errMsg"));
            }

            logger.info("执行时间：" + exctime + "毫秒");
            //打印目标方法的return结果
            logger.info("执行结果: " + result);

            SystemControllerLog controllerLog = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(SystemControllerLog.class);
            logMap.put("ordertype", controllerLog.MODULENAME());
            logMap.put("startdate", startDate);
            logMap.put("starttime", startTime);
            logMap.put("result", result);
            logMap.put("proceedtime", exctime);
            threadPoolTaskExecutor.execute(new SaveLogInfoPool(logMap, sysLogService));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;

//        //System.out.println("环绕通知:前置...");
//        //requestUrl接口路径
//        String interf = "";
//        String userName = "";
//        String url = "";
//        String ip = "";
//        String callClassName = "";
//        Object obj = null;
//        Map<String, Object> logMap = new HashMap<>();
//        Class[] classArgs = null;
//        try {
//            // 得到方法所需的参数，获取访问的方法的参数
//            Object[] args = pjp.getArgs();
//            //todo 增加验证 args不能为空 必须有一个字段token
//            //System.err.println(Arrays.toString(args));
//            //当前时间就是开始访问的时间
//            visitTime = new Date();
//            //具体要访问的类
//            clazz = pjp.getTarget().getClass();
//            //获取访问的方法的名称
//            String methodName = pjp.getSignature().getName();
//            //获取url
//            Signature signature = pjp.getSignature();
//            //方法名
//            callClassName = "[类名]" + clazz.getName() + "[方法名]" + signature.getName();
//            //System.err.println(signature.getClass().getName());
//            //获取类上requestMapping的值
//            //获取方法上的RequestMapping的值
//            RequestMapping methodAnnotation = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(RequestMapping.class);
//            //System.err.println(methodAnnotation.value().toString());
//            if (methodAnnotation != null) {
//                String[] methodValue = methodAnnotation.value();
//                url = classValue[0] + methodValue[0];
//                interf = methodValue[0];
//            }
//
//            //获取访问的ip
//            ip = IPUtil.getIpAddr(request);
//            logMap.put("LOGID", Tools.newId());
//            //访问时间
//            logMap.put("OPERATETIME", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(visitTime));
//            logMap.put("IP", ip);
//            logMap.put("METHOD", request.getMethod());
//            logMap.put("REQUESTURL", url);
//            logMap.put("CALLCLASSNAME", callClassName);
//            //提交参数params
//            logMap.put("PARAMS", Arrays.toString(args));
//            //日志类型
//            logMap.put("TYPE", "info");
//            //第一个对象必须放TOKEN否则获取不到
//            //操作人
//            /**
//             * 1.正常获取token都是默认方法的第一个字段必须放token字段
//             * 2.Login.do特殊,他第一个默认放json数组从中获取token
//             * 3.无token可能是登录等特殊接口或非法接口或没有传递TOKEN
//             **/
//            String token = "";
//            if ("/Login.do".equals(interf)) {
//                JSONObject jsonGet = JSONObject.parseObject(args[0].toString());
//                userName = jsonGet.getString("userName");
//            } else {
//                //判断一下预防空指针影响主程序,一般情况不会报空指针因为拦截器会判断
//                if (args[0] != null && args[0] != "") {
//                    token = args[0].toString();
//                    Object hget = redisUtil.hget(token, RedisConsts.USER_INFO);
//                    JSONObject jsonRedis = JSONObject.parseObject(hget.toString());
//                    userName = jsonRedis.getString("userName");
//                } else {
//                    System.err.println("token空值非法访问");
//                }
//            }
//            //得到手写日志参数数据
//            Map<String, Object> aspMap = getControllerMethodDescription(pjp);
//            logMap.put("USERNAME", userName);
//            logMap.putAll(aspMap);
//
//            //验证传参是否存在sql注入，如有非法词语全部拦截并记录日志
//            String str = String.valueOf(args);
//            if (!IllegalStrFilterUtil.sqlStrFilter(str)) {
//                new RuntimeException(IS_SQL_INJECTION);
//            }
//            if (!IllegalStrFilterUtil.isIllegalStr(str)) {
//                new RuntimeException(UNVALIDATED_INPUT);
//            }
//
//            /*通过TOKEN获取用户信息*/
//            //logMap.put("USERNAME", sysData.getUserInfo().getUserName());
//            //System.err.println("调用proceed之前是前置通知，一旦调用环绕通知后前置通知结束!!!");
//            //明确调用业务层方法
//            obj = pjp.proceed(args);
//            /*//获取别切入点的返回值
//            Object proceed = pjp.proceed();
//            System.err.println("切入点返回值:" + proceed);*/
//            //System.out.println("环绕通知:后置开始...");
//            Date endTime = new Date();
//            //获取访问的时长
//            long time = endTime.getTime() - visitTime.getTime();
//            //方法执行时长(executionTime)
//            logMap.put("EXECUTIONTIME", time);
//            //ThreadManager.getThreadPool().executor(new SaveLogInfoPool(logMap));
//            threadPoolTaskExecutor.execute(new SaveLogInfoPool(logMap, sysLogService));
//            return obj;
//        } catch (Throwable throwable) {
//            System.out.println("环绕通知:异常...");
//            Date endTime = new Date();
//            //获取访问的时长
//            long time = endTime.getTime() - visitTime.getTime();
//            //方法执行时长(executionTime)
//            logMap.put("EXECUTIONTIME", time);
//            //保存异常
//            logMap.put("EXCEPTION", throwable.toString());
//            logMap.put("TYPE", "error");
//            threadPoolTaskExecutor.execute(new SaveLogInfoPool(logMap, sysLogService));
//            throw new RuntimeException(throwable);
//        } finally {
//            //System.out.println("环绕通知:最终...");
//        }
    }


    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     */
    public static Map<String, Object> getControllerMethodDescription(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SystemControllerLog controllerLog = method
                .getAnnotation(SystemControllerLog.class);
        //模块名
        String modulename = controllerLog.MODULENAME();
        //方法名
//        String functionname = controllerLog.FUNCTIONNAME();
        Map<String, Object> map = new HashMap<>();
        map.put("MODULENAME", modulename);
//        map.put("FUNCTIONNAME", functionname);
        return map;
    }

    //保存日志线程池
    public static class SaveLogInfoPool implements Runnable {
        private Map<String, Object> logMap;
        private SysLogsService sysLogService;

        public SaveLogInfoPool(Map<String, Object> logMap, SysLogsService sysLogService) {
            this.logMap = logMap;
            this.sysLogService = sysLogService;
        }

        @Override
        public void run() {
//            List<Map<String, Object>> list = new ArrayList<>();
//            list.add(logMap);
            try {
                sysLogService.saveLog(logMap);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //System.err.println("日志添加:" + (i == 0 ? "成功" : "失败"));
        }
    }
    //前置通知  主要是获取开始时间，执行的类是哪一个，执行的是哪一个方法
    //@Before("execution(* com.easystar.action..*.*(..))")
    //@Before("controllerPointerCut()")

    /**
     * 前置通知 用于拦截Controller层记录用户的操作的开始时间
     *
     * @param jp 切点
     * @throws InterruptedException
     */
    //@Before("controllerAspect()")
    public void doBefore(JoinPoint jp) throws NoSuchMethodException {
//        visitTime = new Date();//当前时间就是开始访问的时间
//        clazz = jp.getTarget().getClass(); //具体要访问的类
//        String methodName = jp.getSignature().getName(); //获取访问的方法的名称
//        Object[] args = jp.getArgs();//获取访问的方法的参数
//
//        //获取具体执行的方法的Method对象
//        if (args == null || args.length == 0) {
//            method = clazz.getMethod(methodName); //只能获取无参数的方法
//        } else {
//            Class[] classArgs = new Class[args.length];
//            for (int i = 0; i < args.length; i++) {
//                classArgs[i] = args[i].getClass();
//            }
//            clazz.getMethod(methodName, classArgs);
//        }
    }

    /**
     * 后置通知
     *
     * @param joinPoint
     */
    //@AfterReturning(pointcut = "controllerAspect()", returning = "objVal")
    @AfterReturning("controllerAspect()")
    public void doAfterReturning(JoinPoint joinPoint) {
       /* RequestMapping methodAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(RequestMapping.class);
        System.err.println(methodAnnotation.value().toString());
        if (methodAnnotation != null) {
            String[] methodValue = methodAnnotation.value();
            //System.err.println(methodValue[0]);
            //System.err.println(classValue[0]);
            String url = methodValue[0];
            System.err.println("是否是登录接口:" + url.contains("login.do"));
        }*/
        //System.err.println("后置通知AfterReturning单独判断条目增删改查........");
    }

    /**
     * 最终通知 用于拦截Controller层记录用户的操作
     *
     * @param jp 切点
     *           告诉编译器忽略 unchecked 警告信息，如使用List，ArrayList等未进行参数化产生的警告信息。
     */

    @SuppressWarnings("unchecked")
    //@After("controllerAspect()")
    public void doAfter(JoinPoint jp) throws Exception {
        System.err.println("最终通知");
//        Date endTime = new Date();
//        //获取访问的时长
//        long time = endTime.getTime() - visitTime.getTime();
//        String url = "";
//        //获取url
//        if (clazz != null && method != null && clazz != SystemLogAspect.class) {
//            //1.获取类上的@RequestMapping("/orders")
//            RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
//            if (classAnnotation != null) {
//                String[] classValue = classAnnotation.value();
//                //2.获取方法上的@RequestMapping(xxx)
//                RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
//                if (methodAnnotation != null) {
//                    String[] methodValue = methodAnnotation.value();
//                    url = classValue[0] + methodValue[0];
//
//                    //获取访问的ip
//                    String ip = request.getRemoteAddr();
//                }
//            }
//        }

    }


    /**
     * 异常通知
     *
     * @param joinPoint
     * @param e
     */
    //@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
       /* Log log = logThreadLocal.get();
        if(log != null){
            log.setType("error");
            log.setException(e.toString());
            new UpdateLogThread(log, logService).start();
        }*/
    }

    public void methodyuan() {
        //获取具体执行的方法的Method对象
      /*  if (args == null || args.length == 0) {
            method = clazz.getMethod(methodName); //只能获取无参数的方法
        } else {
            classArgs = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                //此种方式获取request的class为 org.apache.catalina.connector.RequestFacade所以如果方法传request对象获取的是他的实现类，
                //所以此处不再用此方法获取method
                classArgs[i] = args[i].getClass();
            }
            method = clazz.getMethod(methodName, classArgs);
        }*/
         /*if (clazz != null && method != null && clazz != SystemLogAspect.class) {
                //1.获取类上的@RequestMapping("/orders")
                RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                String[] classValue = {""};
                if (classAnnotation != null) {
                    classValue = classAnnotation.value();
                }
                //2.获取方法上的@RequestMapping(xxx)
                RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
                if (methodAnnotation != null) {
                    String[] methodValue = methodAnnotation.value();
                    System.err.println(classValue[0]);
                    url = classValue[0] + methodValue[0];
                }
                callClassName = "[类名]" + clazz.getName() + "[方法名]" + method.getName();
            }*/
    }
}
